#include <iostream>
#include <vector>
#include <string>
#include <pthread.h>
#include <unistd.h>

#define NUM 4


// 对一个全局变量进行多线程并发--/++操作是否是安全的？不是安全的
// 不管你怎么定义这个全局变量，他一定是在内存中的。初始值设置为1000
// 如果要对全局变量做--操作，本质是对数据做计算。在计算机中，要对数据做计算，必须要在cpu内部做计算。
// 因此第一步先将全局变量读入到cpu的寄存器中。
// 2、在cpu内部做--计算，而--操作会影响自己，所以操作完后还要把计算结果写回到内存。
// 3、将数据写回到内存
// 这三个步骤，每一条都会对应一条汇编操作
int tickets = 1000; //用多线程模拟抢票

class threadData
{
public:
    threadData(int number)
    {
        thread_name = "thread-" + std::to_string(number);
    }

    std::string thread_name;
};

void * getTickets(void* args)
{
    threadData* td = static_cast<threadData*>(args);
    const char* name = td->thread_name.c_str();
    // 逻辑计算和数值计算都要从内存加载到cpu当中，从而导致数据不一致。
    // 解决方式：对共享数据的任何访问，保证任何时候只有一个执行流访问。  --- 互斥（锁）
    while(true)
    {
        // 逻辑计算
        if(tickets > 0)
        {
            usleep(1000);  // 休眠1000微妙，模拟抢票花费时间
            printf("who=%s, get a ticket: %d\n", name, tickets);  //?
            // 数值计算
            tickets--;
        }
        else{
            break;
        }
    }

    printf("%s ... quit\n", name);
    return nullptr;
}

int main()
{   
    // 记录所有线程的id
    std::vector<pthread_t> tids;
    // 记录所有线程的数据
    std::vector<threadData*> thread_datas;
    for(int i = 1; i <= NUM; ++i)
    {
        pthread_t tid;
        threadData* td = new threadData(i);
        thread_datas.push_back(td);
        pthread_create(&tid, nullptr, getTickets, thread_datas[i - 1]);
        tids.push_back(tid);
    }

    for(int i = 0; i < NUM; ++i)
    {
        pthread_join(tids[i], nullptr);
    }

    for(auto td : thread_datas)
    {
        delete td;
    }
    return 0;
}


// 线程切换的时间点是内核到用户返回的时候在做切换检测。但是不一定会切换
// 而sleep的时候，1、既可以保证当前线程被挂起或被唤醒，所以一定会经历从内核到用户的状态切换。
// 2、sleep的过程中，尽量的让当前线程的时间片雷同